﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows.Controls;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Logging;
using System.Windows.Threading;
using System.Windows.Media;
using Util;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;

namespace UWCSE.CompleterVSPackage
{
    abstract class CompletionsGuiHelper : IDisposable
    {
        internal readonly ProcessHandler processHandler;

        private string completionsCancelToken;
        private ObservableCollection<object> results = new ObservableCollection<object>();

        private object resultsThread;
        private System.Windows.Threading.Dispatcher dispatcher;
        private CancellationTokenSource cancellationSource;

        private VirtualizingStackPanel resultsListVsp;
        private readonly ListBox resultsList;

        private Span? matchSpan, forcedMatchSpan;

        private bool moreWhenScrolledDown = false;

        internal readonly ObjectBox<IWpfTextView> activeWpfTextViewBox;

        /// <summary>
        /// Number of results to load a time.
        /// </summary>
        private const int RESULT_PAGE_SIZE = 30;

        internal CompletionsGuiHelper(ProcessHandler processHandler,
            ListBox resultsList, ObjectBox<IWpfTextView> activeWpfTextViewBox,
            Span? matchSpan = null)
        {
            this.processHandler = processHandler;
            this.resultsList = resultsList;
            this.activeWpfTextViewBox = activeWpfTextViewBox;
            this.forcedMatchSpan = matchSpan;
            this.ResetResultsThread();
        }

        internal ObservableCollection<object> Results { get { return results; } }


        internal void ResetResultsThread()
        {
            this.resultsThread = new object();
            //this.resultsThread.Name = "Results computation thread";
            //resultsThread.Start();
        }

        private List<Result> resultsThreadToAdd = new List<Result>();

        internal void AddToResults()
        {
            lock (resultsThread)
            {
                if (results.Count == 0)
                {
                    return;
                }
                int lastIndex = results.Count - 1;
                object loading = results[lastIndex];
                if (!(loading is string))
                {
                    resultsThreadToAdd.Clear();
                    return;
                }
                results.RemoveAt(lastIndex);
                bool firstResults = results.Count == 0;
                bool nullResult = false;
                foreach (Result c in resultsThreadToAdd)
                {
                    if (c == null)
                    {
                        nullResult = true;
                    }
                    else
                    {
                        results.Add(c);
                    }
                }
                resultsThreadToAdd.Clear();
                //if (completions != null)
                if (!nullResult)
                {
                    results.Add(loading);
                }
                if (firstResults && results.Count > 0)
                {
                    resultsList.SelectedIndex = 0;
                    resultsList.ScrollIntoView(results[0]);
                    ItemContainerGenerator icg = resultsList.ItemContainerGenerator;
                    ListBoxItem item =
                        (ListBoxItem)resultsList.ItemContainerGenerator.ContainerFromIndex(0);
                    if (item != null)
                    {
                        item.Focus();
                    }
                    else
                    {
                    }
                }
            }
        }

        public bool IsReady { get; private set; }

        internal void DoCompletionAction(string query = null)
        {
            Logger.PrintLineWithTime("Key combination pressed");
            IsReady = true;

            if (dispatcher == null)
            {
                dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
            }

            DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE;
            Document doc = dte.ActiveDocument;

            try
            {
                activeWpfTextViewBox.Contents = dte.GetCurrentIWpfTextView();
                processHandler.UpdateCurrentDocument(doc);
            }
            catch (NullReferenceException)
            {
                // Not actually in a text editor document.
                return;
            }

            var oldCompletionsCancelToken = completionsCancelToken;
            if (oldCompletionsCancelToken != null)
            {
                processHandler.Cancel(oldCompletionsCancelToken);
            }
            var oldCancellationSource = cancellationSource;
            if (oldCancellationSource != null)
            {
                oldCancellationSource.Cancel();
            }
            cancellationSource = new CancellationTokenSource();

            var loadDocTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
            {
                // TODO Remember when we need to force reloading?
                this.processHandler.FullyLoadDocument(doc, true);
            }, cancellationSource.Token);

            var span = activeWpfTextViewBox.Contents.Selection.StreamSelectionSpan.SnapshotSpan.Span;
            int start = span.Start;
            int end = span.End;

            var startLine = activeWpfTextViewBox.Contents.TextSnapshot.GetLineFromPosition(start);
            int startCol = start - startLine.Start.Position;
            int startLineNum = startLine.LineNumber;

            var endLine = activeWpfTextViewBox.Contents.TextSnapshot.GetLineFromPosition(end);
            int endCol = end - endLine.Start.Position;
            int endLineNum = endLine.LineNumber;


            //Solution sln = dte.Solution;
            TextDocument txt = doc.Object() as TextDocument;

            var selection = (EnvDTE.TextSelection)doc.Selection;
            int doc_start = selection.TopPoint.AbsoluteCharOffset;
            int doc_end = selection.BottomPoint.AbsoluteCharOffset;

            if (doc_start != start || doc_end != end)
            {
                ToString();
            }

            BeforeShow();
            lock (resultsThread)
            {
                this.matchSpan = null;
                results.Add("Loading...");
                maxVisibleItem = 0;
                resultsList.SelectedIndex = 0;
            }
            Show();

            loadDocTask.ContinueWith(_ =>
            {
                // If the cursor hasn't moved...
                if (activeWpfTextViewBox.Contents.Selection.StreamSelectionSpan.SnapshotSpan.Span.Equals(span))
                {
                    Span matchSpan;
                    int resultsAdded = 0;
                    moreWhenScrolledDown = false;
                    ProcessHandler.TypeNameHandler typeHandler = NotifyMatchType;
                    ProcessHandler.RewriteHandler rewriteHandler =
                        (matchLineNum, matchCol, matchEndLineNum, matchEndCol, rewrite) =>
                        {
                            if (rewrite == null)
                            {
                                // Not a partial expression.
                                dispatcher.InvokeIfRequired(() =>
                                {
                                    LogLineAboutCompletion("CLOSING WINDOW: Not a partial expression");
                                    NotPartialExpression();
                                }, DispatcherPriority.DataBind);
                            }
                            else
                            {
                                var matchLine = activeWpfTextViewBox.Contents.TextSnapshot.GetLineFromLineNumber(matchLineNum);
                                var matchEndLine = activeWpfTextViewBox.Contents.TextSnapshot.GetLineFromLineNumber(matchEndLineNum);
                                int matchStart = matchLine.Start.Position + matchCol;
                                int matchEnd = Math.Min(matchEndLine.Start.Position + matchEndCol - 1,
                                                        matchEndLine.End.Position - 1);
                                int matchLength = matchEnd - matchStart + 1;
                                matchSpan = new Span(matchStart, matchLength);
                                this.matchSpan = null;

                                dispatcher.InvokeIfRequired((Action)(() =>
                                {
                                    string matchText = activeWpfTextViewBox.Contents.TextSnapshot.GetText(matchSpan);
                                    this.matchSpan = HandleRewriteInfo(matchSpan, matchText, rewrite);
                                }),
                                    DispatcherPriority.DataBind);
                            }
                        };
                    ProcessHandler.ResultHandler resultHandler =
                        result =>
                        {
                            lock (resultsThread)
                            {
                                if (result != null)
                                {
                                    LogLineAboutCompletion(
                                        "RESULT (#" + (++resultsAdded) + "): " + result);
                                }
                                resultsThreadToAdd.Add(result);
                                dispatcher.InvokeIfRequired(AddToResults,
                                    DispatcherPriority.DataBind);
                                if (result != null && (resultsAdded % RESULT_PAGE_SIZE == 0 || moreWhenScrolledDown))
                                {
                                    dispatcher.InvokeIfRequired(() =>
                                    {
                                        if (IsResultsListScrolledDown())
                                        {
                                            this.processHandler.RequestMoreCompletionsAsync(this.completionsCancelToken,
                                                cancellationSource.Token, results.Count);
                                            moreWhenScrolledDown = false;
                                        }
                                        else
                                        {
                                            moreWhenScrolledDown = true;
                                        }
                                    }, DispatcherPriority.Background);
                                }
                            }
                        };
                    this.processHandler.GetCompletions(startLineNum, startCol,
                                                       endLineNum, endCol,
                                                       query,
                                                       token => { this.completionsCancelToken = token; },
                                                       rewriteHandler, typeHandler,
                                                       resultHandler);

                }
            }, cancellationSource.Token);
        }

        abstract internal Span HandleRewriteInfo(Span matchSpan,
                                                 string matchText, string rewrite);

        abstract internal void NotifyMatchType(string typeStr);

        abstract internal void NotPartialExpression();

        abstract internal void Show();

        abstract internal void BeforeShow();


        internal void CancelCompletion()
        {
            matchSpan = null;
            if (completionsCancelToken != null)
            {
                processHandler.Cancel(completionsCancelToken);
                if (maxVisibleItem == 0)
                {
                    if (resultsListVsp == null)
                    {
                        ListBoxItem lbi = resultsList.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
                        resultsListVsp = VisualTreeHelper.GetParent(lbi) as VirtualizingStackPanel;
                    }
                    VirtualizingStackPanel vsp = resultsListVsp;
                    int FirstVisibleItem = (int)vsp.VerticalOffset;
                    int VisibleItemCount = (int)vsp.ViewportHeight;
                    int LastVisibleItem = VisibleItemCount + FirstVisibleItem + 1;
                    maxVisibleItem = Math.Max(maxVisibleItem,
                        Math.Min(LastVisibleItem, results.Count));
                }
                LogLineAboutCompletion("Last result seen: #" + maxVisibleItem);
            }
            if (cancellationSource != null)
            {
                cancellationSource.Cancel();
            }
            dispatcher.BeginInvoke(
                (Action)delegate()
                {
                    lock (resultsThread)
                    {
                        completionsCancelToken = null;
                        cancellationSource = null;
                        results.Clear();
                        resultsThreadToAdd.Clear();
                    }
                }, DispatcherPriority.DataBind);
        }

        internal void DoReplace()
        {
            if (activeWpfTextViewBox.Contents != null
                && matchSpan.HasValue
                && resultsList.SelectedItem != null)
            {
                if (resultsList.SelectedItem is Result)
                {
                    string completionText = ((Result)resultsList.SelectedItem).CompletionText;
                    var undoHist = processHandler.CurrentUndoHistory;
                    ITextUndoTransaction transaction = null;
                    if (undoHist != null)
                    {
                        transaction = undoHist.CreateTransaction(
                            "Inserted completion '" + completionText + "'");
                    }
                    else
                    {
                        LogLineAboutCompletion("FAILED TO TRACK UNDO for DoReplace");
                    }
                    activeWpfTextViewBox.Contents.TextBuffer.Replace(
                        (forcedMatchSpan.HasValue ? forcedMatchSpan : matchSpan).Value,
                        completionText);
                    if (transaction != null)
                    {
                        transaction.AddUndo(new LoggingUndoPrimitive(
                            "Replace for completion " + CompletionsCancelToken));
                        transaction.Complete();
                    }

                    LogLineAboutCompletion("CHOOSE RESULT (#"
                        + (resultsList.SelectedIndex + 1) + "): "
                        + resultsList.SelectedItem);
                    LogLineAboutCompletion("CLOSING WINDOW: User choose a match");
                    FinishedReplace();
                }
            }
        }

        internal abstract void FinishedReplace();

        private int maxVisibleItem = 0;
        private bool IsResultsListScrolledDown()
        {
            if (results.Count == 0)
                return false;
            else if (resultsList.Items.Count <= RESULT_PAGE_SIZE && results[results.Count - 1] is string)
                return true;
            if (resultsListVsp == null)
            {
                ListBoxItem lbi = resultsList.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
                if (lbi == null)
                {
                    return true; // XXX TODO handle QueryWindow right here.
                }
                resultsListVsp = VisualTreeHelper.GetParent(lbi) as VirtualizingStackPanel;
            }
            VirtualizingStackPanel vsp = resultsListVsp;
            int FirstVisibleItem = (int)vsp.VerticalOffset;
            int VisibleItemCount = (int)vsp.ViewportHeight;
            int LastVisibleItem = VisibleItemCount + FirstVisibleItem + 1;
            maxVisibleItem = Math.Max(maxVisibleItem,
                Math.Min(LastVisibleItem, results.Count));
            if (LastVisibleItem + RESULT_PAGE_SIZE >= resultsList.Items.Count)
                return true;
            return false;
        }

        public string CompletionsCancelToken { get { return completionsCancelToken; } }

        internal void resultsList_ScrollChanged()
        {
            if (resultsListVsp == null && results.Count > 0)
            {
                ListBoxItem lbi = resultsList.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
                if (lbi != null)
                {
                    resultsListVsp = VisualTreeHelper.GetParent(lbi) as VirtualizingStackPanel;
                }
            }
            if (this.completionsCancelToken != null && moreWhenScrolledDown && IsResultsListScrolledDown())
            {
                this.processHandler.RequestMoreCompletionsAsync(
                    this.completionsCancelToken,
                    cancellationSource.Token,
                    results.Count);
            }
        }

        internal void LogLineAboutCompletion(string line, string compId = null)
        {
            if (compId == null)
            {
                compId = completionsCancelToken;
            }
            ProcessHandler.LogLineAboutCompletion(compId, line);
        }

        public System.Windows.Threading.Dispatcher Dispatcher { get { return dispatcher; } }

        public Span? MatchSpan { get { return forcedMatchSpan.HasValue ? forcedMatchSpan : matchSpan; } }

        void IDisposable.Dispose()
        {
            cancellationSource.Dispose();
        }
    }
}
